package com.snail.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.snail.constants.RedisConstants;
import com.snail.exception.BusinessException;
import com.snail.exception.ErrorCode;
import com.snail.mapper.SysUserMapper;
import com.snail.model.dto.LoginResultDTO;
import com.snail.model.dto.UpdatePasswordDTO;
import com.snail.model.pojo.SysRole;
import com.snail.model.pojo.SysUser;
import com.snail.model.pojo.SysUserRole;
import com.snail.service.BizUserQuestionCollectService;
import com.snail.service.SysRoleService;
import com.snail.service.SysUserRoleService;
import com.snail.service.SysUserService;
import com.snail.utils.PermissionHelperUtils;
import com.snail.utils.UserValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author 蔡徐坤
 * @Date 2025/7/15 0:08
 * @Description: 继承的ServiceImpl，是为了避免重复写一些基础的方法，比如save、update、delete等
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    /**
     * 注入验证用户的封装工具类
     */
    @Autowired
    private UserValidator userValidator;

    @Autowired
    private SysRoleService sysRoleService;

    @Autowired
    private PermissionHelperUtils permissionHelperUtils;

    @Autowired
    private SysUserRoleService userRoleService;

    @Autowired
    private SysUserRoleService sysUserRoleService;

    @Autowired
    private RedisTemplate redisTemplate; // 注入字符串专用的模板
    @Autowired
    private BizUserQuestionCollectService bizUserQuestionCollectService;//绑定收藏夹

    /**
     * 注册用户
     *
     * @param sysUser 用户对
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(SysUser sysUser) {
        // 参数校验  判断用户名和密码是否为空
        userValidator.validateUserParams(sysUser);

        // 检查用户名是否已存在  this指的是当前UserService的实例 即是UserServiceImpl
        userValidator.checkUsernameIsExist(sysUser.getUsername(), this);

        // 密码加密
        String encodedPassword = SaSecureUtil.md5(sysUser.getPassword());
        sysUser.setPassword(encodedPassword);

        // 设置创建人和更新人  注册的时候第一此手动注入
        if (!StpUtil.isLogin()) {
            sysUser.setCreateBy(sysUser.getUsername());
            sysUser.setUpdateBy(sysUser.getUsername());
        }

        // 保存用户  其中的创建人和h更新人都是当前登录的用户
        this.save(sysUser);

        //默认用户注册的时候为普通用户，即为user
        SysRole normalRole = sysRoleService.getOne(
                new LambdaQueryWrapper<SysRole>()
                        .eq(SysRole::getCode, "user")
                        .last("LIMIT 1")
        );

        //给注册的新用户分配角色
        if (normalRole != null) {
            SysUserRole userRole = new SysUserRole();
            userRole.setUserId(sysUser.getUserId());
            userRole.setRoleId(normalRole.getId());
            bizUserQuestionCollectService.addCollectFolder("默认收藏夹");
            userRoleService.save(userRole);
        }

    }

    /**
     * 用户登录方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginResultDTO userLogin(SysUser sysUser) {
        return commonLogin(sysUser, false);
    }

    /**
     * 管理员登录方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginResultDTO adminLogin(SysUser sysUser) {
        return commonLogin(sysUser, true);
    }


    /**
     * 用户登录
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public LoginResultDTO commonLogin(SysUser sysUser, boolean requireAdmin) {
        // 提取出用户名，用以标记登录失败次数过多的用户
        String username = sysUser.getUsername();
        // 拼接失败次数的key
        String failCountKey = RedisConstants.SYS_LOGIN_FAIL_COUNT + username;
        // 判断是否在黑名单
        String blackKey = RedisConstants.SYS_BLACK_LIST + username;
        Boolean isBlacklisted = redisTemplate.hasKey(blackKey);
        if (Boolean.TRUE.equals(isBlacklisted)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "登录失败次数过多，请10分钟后再试");
        }

        // 参数校验  判断用户名和密码是否为空
        userValidator.validateUserParams(sysUser);
        // 检查用户名是否已存在 不存在则抛出异常
        SysUser userExist = userValidator.getUserByUsername(username, this);
        // 提取用户id
        Long userId = userExist.getUserId();

        // 密码加密
        String md5Password = SaSecureUtil.md5(sysUser.getPassword());
        if (!userExist.getPassword().equals(md5Password)) {
            // 增加失败次数
            Long count = redisTemplate.opsForValue().increment(failCountKey);
            redisTemplate.expire(failCountKey, 10L, TimeUnit.MINUTES); // 每次失败都刷新10分钟

            if (count != null && count >= 5) {
                // 加入黑名单，设置10分钟
                redisTemplate.opsForValue().set(blackKey, "1", 10L, TimeUnit.MINUTES);
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "登录失败次数过多，请10分钟后再试");
            }
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
        }

        //提取强制修改密码标志
        Boolean forcePasswordChange = userExist.getForcePasswordChange();

        // 登录成功，清除黑名单与失败记录
        redisTemplate.delete(failCountKey);
        redisTemplate.delete(blackKey);

        //查看用户的角色
        List<String> roleCodes = sysUserRoleService.getRoleCodesByUserId(userId);
        //查看用户的权限
        List<Long> roleIds = sysUserRoleService.getRoleIdsByUserId(userId);

        // 如果是管理员登录，检查是否有管理员权限
        if (requireAdmin && !roleIds.contains(1L)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "非管理员账号，无法登录");
        }

        // 判断是否为管理员（角色ID=1是管理员）
        boolean isAdmin = roleIds.contains(1L);

        List<String> permissionCodes = permissionHelperUtils.getUserPermissionCodes(userId);
        // 登录并返回 token
        StpUtil.login(userId);

        // 创建会话并设置个用户名
        StpUtil.getSession().set("username", username)
                .set("permissionCodes", permissionCodes)
                .set("isAdmin", isAdmin);
        String tokenValue = StpUtil.getTokenValue();

        //等有个人信息页面的时候再进行验证
        if (Boolean.TRUE.equals(forcePasswordChange)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "请先修改默认密码",
                    Map.of("userId", userId, "forcePasswordChange", true, "username", username, "tokenValue", tokenValue));

        }
//        redisTemplate.opsForValue().set(RedisConstants.
//                        SYS_LOGIN_TOKEN_KEY + userId,
//                tokenValue, RedisConstants.SYS_LOGIN_TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
        return new LoginResultDTO(tokenValue, userId, roleCodes, isAdmin, forcePasswordChange);
    }

    /**
     * 获取当前登录用户信息
     */
    @Override
    public SysUser getUser() {
        // 获取当前登录用户的id
        Long userId = StpUtil.getLoginIdAsLong();
        // 根据id查询用户信息
        SysUser sysUser = this.getById(userId);
        return sysUser;
    }

    /**
     * 获取所有用户信息
     */
    @Override
    public List<SysUser> findAllUsers() {
        return this.list();
    }

    /**
     * 根据用户id获取用户信息
     */
    @Override
    public SysUser getByUserId(Long userId) {
        return this.getById(userId);
    }

    /**
     * 更新用户信息
     */
    @Override
    public void updateUser(SysUser sysUser) {
        // 查询现有用户信息
        if (sysUser.getUserId() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户id不能为空");
        }
        SysUser existingUser = this.getById(sysUser.getUserId());
        if (existingUser == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
        }

        // 如果传了密码且不为空，则加密
        if (StrUtil.isNotBlank(sysUser.getPassword())) {
            String encodedPassword = SaSecureUtil.md5(sysUser.getPassword());
            sysUser.setPassword(encodedPassword);
        } else {
            // 如果密码为空，保留原密码
            sysUser.setPassword(existingUser.getPassword());
        }

        this.updateById(sysUser);
    }


    /**
     * 修改密码
     */
    @Override
    public void updatePassword(UpdatePasswordDTO userDTO) {
        // 查询现有用户信息
        long userId = StpUtil.getLoginIdAsLong();
        SysUser existingUser = this.getById(userId);
        System.out.println("existingUser:查看现在登录用户的信息" + existingUser);
        // 判断是否需要强制修改密码
        boolean isForceChange = Boolean.TRUE.equals(existingUser.getForcePasswordChange());
        if (isForceChange) {
            if (StringUtils.isBlank(userDTO.getNewPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "新密码不能为空");
            }
        } else {
            if (!isForceChange) {
                // 判断旧密码是否正确只有非强制修改密码才需要判断
                String oldPassword = userDTO.getOldPassword();
                String oldMd5 = SaSecureUtil.md5(oldPassword);
                // 如果旧密码跟数据库的密码不一致，则抛出异常
                if (!existingUser.getPassword().equals(oldMd5)) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "旧密码错误");
                }
            }
        }

        // 如果旧密码正确，则更新密码
        String newPassword = userDTO.getNewPassword();
        String newMd5 = SaSecureUtil.md5(newPassword);
        existingUser.setPassword(newMd5);
        existingUser.setForcePasswordChange(false); // 重置标志
        this.updateById(existingUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(Long userId) {
        // 1. 查询用户是否存在
        SysUser user = this.getById(userId);
        if (user == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
        }

        // 2. 设置默认密码并加密（开发阶段使用123456）
        String defaultPassword = "123456";
        String encryptedPwd = SaSecureUtil.md5(defaultPassword);

        // 3. 更新密码并设置强制修改标志
        SysUser updateUser = new SysUser();
        updateUser.setUserId(userId);
        updateUser.setPassword(encryptedPwd);
        updateUser.setForcePasswordChange(true); // 需要添加这个字段到SysUser实体

        this.updateById(updateUser);

    }

    /**
     * 删除用户信息
     */
    @Override
    public void deleteUser(Long userId) {
        this.removeById(userId);
    }


}

